package com.zqweb.gulimall.essearch.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.zqweb.common.es.SkuESModel;
import com.zqweb.common.utils.R;
import com.zqweb.gulimall.essearch.feign.ProductFeignService;
import com.zqweb.gulimall.essearch.config.ESClientConfig;
import com.zqweb.gulimall.essearch.constant.EsConstant;
import com.zqweb.gulimall.essearch.service.MallSearchService;
import com.zqweb.gulimall.essearch.vo.AttrResponseVo;
import com.zqweb.gulimall.essearch.vo.BrandVo;
import com.zqweb.gulimall.essearch.vo.SearchParam;
import com.zqweb.gulimall.essearch.vo.SearchResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.NestedQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class MallSearchServiceImpl implements MallSearchService {
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Autowired
    private ProductFeignService productFeignService;


    @Override
    // 传入检索参数对象，获取检索结果
    public SearchResult searchByParams(SearchParam searchParam) {
        // 1、创建并构建检索请求对象
        SearchRequest searchRequest = this.getSearchRequest(searchParam);
        // 2、根据构建的检索请求对象，执行检索，并获取到检索响应对象
        SearchResult searchResult = new SearchResult();
        try {// 包在try-catch语句体里头
            SearchResponse searchResponse = restHighLevelClient.search(searchRequest, ESClientConfig.COMMON_OPTIONS);
            // 3、分析检索结果，并将相应结果封装为前端所需的SearchResult类型
            searchResult = getSearchResult(searchResponse,searchParam);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return searchResult;
    }

    // 构建SearchRequest对象
    private SearchRequest getSearchRequest(SearchParam searchParam){
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        /**
         * 1、模糊匹配，过滤（按照属性，分类，品牌，价格区间，库存）
         * 2、排序，分页，高亮，聚合分析
         */
        // ===================== 模糊匹配，过滤（按照属性，分类，品牌，价格区间，库存） =====================
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        // 构建keyword
        String keyword = searchParam.getKeyword();
        if(!StringUtils.isEmpty(keyword)){
            boolQuery.must(QueryBuilders.matchQuery("skuTitle",keyword));
        }

        // 构建catalogId
        Integer catelog3Id = searchParam.getCatelog3Id();
        if(catelog3Id != null){
            boolQuery.filter(QueryBuilders.termQuery("catalogId",searchParam.getCatelog3Id()));
        }

        // 构建brandId
        List<Long> brandIds = searchParam.getBrandId();
        if(brandIds != null && brandIds.size() > 0){
            boolQuery.filter(QueryBuilders.termsQuery("brandId",brandIds));
        }

        // 构建hasStock
        Integer hasStock = searchParam.getHasStock();
        if(hasStock != null){
            /*
             为null时，不管其值是否为1（是否有库存），都去检索获取，即不走该库存的过滤条件
             而只要带上了hasStock的检索条件，就要进行条件过滤，检索有库存的记录
              */
            boolQuery.filter(QueryBuilders.termQuery("hasStock",hasStock == 1));
        }

        // 构建skuPrice
        String skuPrice = searchParam.getSkuPrice();
        if(!StringUtils.isEmpty(skuPrice)){
            String[] s = skuPrice.split("_");
            if(s.length == 2){
                boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").gte(s[0]).lte(s[1]));
            }else if(s.length == 1){
                if(skuPrice.startsWith("_")){
                    boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").lte(s[0]));
                }else if(skuPrice.endsWith("_")){
                    boolQuery.filter(QueryBuilders.rangeQuery("skuPrice").gte(s[0]));
                }
            }
        }

        // 构建attrs
        List<String> attrs = searchParam.getAttrs();
        if(attrs != null && attrs.size() > 0){
            for (String attr : attrs) {
                BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
                String[] s = attr.split("_");
                String[] attrValue = s[1].split(":");
                boolQueryBuilder.must(QueryBuilders.termQuery("attrs.attrId",s[0]));
                boolQueryBuilder.must(QueryBuilders.termsQuery("attrs.attrValue",attrValue));
                // 嵌入式query的builder
                NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs", boolQueryBuilder, ScoreMode.None);
                boolQuery.filter(nestedQueryBuilder);
            }
        }
        // 整合所有的查询条件（模糊匹配、过滤项）
        searchSourceBuilder.query(boolQuery);


        //  ===================== 排序、分页、高亮显示 ==========================
        String sort = searchParam.getSort();// sort=saleCount_asc/desc
        // 排序
        if(!StringUtils.isEmpty(sort)){
            String[] s = sort.split("_");
            SortOrder order = s[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC;
            searchSourceBuilder.sort(s[0],order);
        }
        // 分页
        Integer pageNum = searchParam.getPageNum();
        searchSourceBuilder.from((pageNum - 1) * EsConstant.PRODUCT_PAGESIZE);// from值
        searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);// size值

        // 高亮显示（keyword字段）
        if(!StringUtils.isEmpty(keyword)){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");// 指定高亮的字段
            highlightBuilder.preTags("<b style='color:red'>");// 指定高亮的前缀
            highlightBuilder.postTags("</b>");// 指定高亮的后缀
            searchSourceBuilder.highlighter(highlightBuilder);
        }

        // =============== 聚合分析 ====================
        // 1、根据brandId生成的品牌聚合
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brandAgg").field("brandId").size(50);
        // 根据品牌聚合生成子聚合，对应每个brandId的品牌
        // brandNameAgg
        brandAgg.subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName").size(1));
        // brandImgAgg
        brandAgg.subAggregation(AggregationBuilders.terms("brandImgAgg").field("brandImg").size(1));
        searchSourceBuilder.aggregation(brandAgg);

        // 2、根据catalogId生成的分类聚合
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalogAgg").field("catalogId").size(50);
        // 根据分类聚合生成子聚合，对应每个catalogId的分类
        catalogAgg.subAggregation(AggregationBuilders.terms("catalogNameAgg").field("catalogName").size(1));
        searchSourceBuilder.aggregation(catalogAgg);

        // 3、根据嵌入式字段attrs生成的属性聚合
        NestedAggregationBuilder nestedAggregationBuilder = AggregationBuilders.nested("attrs", "attrs");
        // attrIdAgg 属性id聚合
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg").field("attrs.attrId").size(50);
        // 根据每个属性id得到的attrNameAgg 属性名聚合
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName").size(1));
        // 根据每个属性id得到的attrValueAgg 属性值聚合
        attrIdAgg.subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue").size(50));
        nestedAggregationBuilder.subAggregation(attrIdAgg);
        searchSourceBuilder.aggregation(nestedAggregationBuilder);

        // 日志打印构建出的DSL语句
        log.debug("构建的DSL语句 {}",searchSourceBuilder.toString());

        // 构建检索请求对象，并将其返回
        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX}, searchSourceBuilder);
        return searchRequest;
    }

    // SearchResponse对象的分析和封装
    private SearchResult getSearchResult(SearchResponse searchResponse,SearchParam searchParam){
        SearchResult searchResult = new SearchResult();
        // 1、封装商品信息
        SearchHits searchHits = searchResponse.getHits();
        SearchHit[] hits = searchHits.getHits();
        List<SkuESModel> skuESModels = new ArrayList<>();
        if(hits != null && hits.length > 0){
            for (SearchHit hit : hits) {
                SkuESModel skuESModel = JSON.parseObject(hit.getSourceAsString(), SkuESModel.class);
                // 会检索关键字keyword设置高亮显示
                if(!StringUtils.isEmpty(searchParam.getKeyword())){
                    HighlightField highlightField = hit.getHighlightFields().get("skuTitle");
                    String skuTitle = highlightField.getFragments()[0].string();
                    skuESModel.setSkuTitle(skuTitle);
                }
                skuESModels.add(skuESModel);
            }
        }
        searchResult.setProducts(skuESModels);

        // 2、封装分页属性信息
        Integer pageNum = searchParam.getPageNum();
        long total = searchHits.getTotalHits().value;
        int flag = (int) total / EsConstant.PRODUCT_PAGESIZE;
        Integer totalPages = total % EsConstant.PRODUCT_PAGESIZE == 0 ? flag : flag + 1;
        List<Integer> pageBody = new ArrayList<>();
        for (int i = 1; i <= totalPages; i++) {
            pageBody.add(i);
        }
        searchResult.setPageNum(pageNum);
        searchResult.setTotal(total);
        searchResult.setTotalPages(totalPages);
        searchResult.setPageBody(pageBody);

        // 3、封装品牌信息
        ParsedLongTerms brandAgg = searchResponse.getAggregations().get("brandAgg");
        List<? extends Terms.Bucket> brandAggBuckets = brandAgg.getBuckets();
        if(brandAggBuckets != null && brandAggBuckets.size() > 0){
            List<SearchResult.BrandVo> brandVos = new ArrayList<>();
            for (Terms.Bucket brandBucket : brandAggBuckets) {
                SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
                long brandId = brandBucket.getKeyAsNumber().longValue();
                ParsedStringTerms brandNameAgg = brandBucket.getAggregations().get("brandNameAgg");
                String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
                ParsedStringTerms brandImgAgg = brandBucket.getAggregations().get("brandImgAgg");
                String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
                brandVo.setBrandId(brandId);
                brandVo.setBrandName(brandName);
                brandVo.setBrandImg(brandImg);
                brandVos.add(brandVo);
            }
            searchResult.setBrands(brandVos);
        }

        // 4、封装分类信息
        ParsedLongTerms catalogAgg = searchResponse.getAggregations().get("catalogAgg");
        List<? extends Terms.Bucket> catalogBuckets = catalogAgg.getBuckets();
        if(catalogBuckets != null && catalogBuckets.size() > 0){
            List<SearchResult.CatelogVo> catelogVos = new ArrayList<>();
            for (Terms.Bucket catalogBucket : catalogBuckets) {
                SearchResult.CatelogVo catelogVo = new SearchResult.CatelogVo();
                long catalogId = catalogBucket.getKeyAsNumber().longValue();
                ParsedStringTerms catalogNameAgg = catalogBucket.getAggregations().get("catalogNameAgg");
                String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
                catelogVo.setCatelogId(catalogId);
                catelogVo.setCatelogName(catalogName);
                catelogVos.add(catelogVo);
            }
            searchResult.setCatelogs(catelogVos);
        }

        // 5、封装属性信息
        ParsedNested attrs = searchResponse.getAggregations().get("attrs");
        ParsedLongTerms attrIdAgg = attrs.getAggregations().get("attrIdAgg");
        List<? extends Terms.Bucket> idAggBuckets = attrIdAgg.getBuckets();
        if(idAggBuckets != null && idAggBuckets.size() > 0){
            List<SearchResult.AttrVo> attrVos = new ArrayList<>();
            for (Terms.Bucket idAggBucket : idAggBuckets) {
                SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
                long attrId = idAggBucket.getKeyAsNumber().longValue();
                ParsedStringTerms attrNameAgg = idAggBucket.getAggregations().get("attrNameAgg");
                String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
                ParsedStringTerms attrValueAgg = idAggBucket.getAggregations().get("attrValueAgg");
                List<String> attrValues = attrValueAgg.getBuckets().stream().map(item -> {
                    String attrValue = ((Terms.Bucket) item).getKeyAsString();
                    return attrValue;
                }).collect(Collectors.toList());
                attrVo.setAttrId(attrId);
                attrVo.setAttrName(attrName);
                attrVo.setAttrValue(attrValues);
                attrVos.add(attrVo);
            }
            searchResult.setAttrs(attrVos);
        }

        // 6、封装面包屑导航数据
        // 1、面包屑导航之属性封装
        List<String> searchParamAttrs = searchParam.getAttrs();
        if(searchParamAttrs != null && searchParamAttrs.size() > 0){
            List<Long> searchedAttrIds = new ArrayList<>();
            List<SearchResult.NavVo> navVos = searchParamAttrs.stream().map(attr -> {
                SearchResult.NavVo navVo = new SearchResult.NavVo();
                // 检索参数的attr内容格式：1_5存:6寸
                String[] s = attr.split("_");
                Long attrId = Long.parseLong(s[0]);
                searchedAttrIds.add(attrId);// 将检索过的attrId加入相应集合中
                String navValue = s[1];

                R r = productFeignService.attrInfo(attrId);
                String navName = "";
                if (r.getCode() == 0) {// 如果成功查询指定attrId对应的attrName，则赋值给navName
                    AttrResponseVo attrResponseVo = r.getData("attr", new TypeReference<AttrResponseVo>() {
                    });
                    navName = attrResponseVo.getAttrName();
                } else {
                    navName = s[0];// 否则，将attrId赋给navName
                }
                navVo.setNavName(navName);
                navVo.setNavValue(navValue);

                /*
                    设置面包屑导航中各个导航项的链接地址，主要为了清除attrs参数检索部分对应的访问路径
                    但是要注意：若请求路径中包含中文、空格等特殊字符，浏览器会自行进行1个编解码，将编码后
                    的路径string传给后台，后台若要根据attrs部分对应的访问路径进行替换、清除，首先就得自行
                    作1个编码处理（包括空格，Java端---对应 + 和 浏览器端---对应 %20 的编码处理还不一样）
                    然后才能进行匹配、替换和清除
                 */
                String queryString = searchParam.get_queryString();
                String resString = this.getKeyPathRemoved(queryString, attr, "attrs");

                // 将当前attrs部分清除后的路径设置到该面包屑导航项的link属性中
                navVo.setLink("http://search.gulimall.com/list.html?" + resString);

                return navVo;
            }).collect(Collectors.toList());
            searchResult.setNavVos(navVos);// 设置面包屑导航项集合
            searchResult.setSearchedAttrIds(searchedAttrIds);// 设置已检索过的attrIds
        }
        // 2、面包屑导航之品牌封装
        List<Long> brandIds = searchParam.getBrandId();
        if(brandIds != null && brandIds.size() > 0){
            R r = productFeignService.brandIdsInfo(brandIds);
            // 获取到原生设置好的navVos，然后不断拼接其他面包屑导航项
            List<SearchResult.NavVo> navVos = searchResult.getNavVos();
            if(r.getCode() == 0){
                List<BrandVo> brands = r.getData("brands", new TypeReference<List<BrandVo>>() {
                });
                if(brands != null && brands.size() > 0){
        // 需要判断navVos是否为空，因为前面封装属性时，检索路径得到的属性集合可能为空（前端的x功能），根本没有设置navVos
                    if(navVos == null){
                        navVos = new ArrayList<>();
                    }
                    for (BrandVo brand : brands) {
                        SearchResult.NavVo navVo = new SearchResult.NavVo();
                        navVo.setNavName("品牌");
                        navVo.setNavValue(brand.getBrandName());
                        String queryString = searchParam.get_queryString();
                        String resString = getKeyPathRemoved(queryString, brand.getBrandId() + "", "brandId");
                        navVo.setLink("http://search.gulimall.com/list.html?" + resString);
                        navVos.add(navVo);
                    }
                }
            }
            // 将面包屑导航项重新设置回检索结果对象中
            searchResult.setNavVos(navVos);

        }
        // TODO 3、面包屑导航之分类封装（）



        return searchResult;
    }

    // 将指定key=value部分对应的路径从原有路径中删除（抽离出来生成1个方法，供不同key使用）
    private String getKeyPathRemoved(String queryString,String attr,String key){
        String resString = queryString;
        try {
            // 字符串的replace方法不影响原串，要重新接收
            String encodedAttr = URLEncoder.encode(attr, "UTF-8");
            encodedAttr = encodedAttr.replace("+","%20");// 空格编码处理
            resString = resString.replace("&" + key + "=" + encodedAttr,"");// attrs参数访问路径部分
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return resString;
    }

}
